#include <stdio.h>

#include "../log.h"

#include "tables.h"
#include "mips.h"
#include "dbg.h"

#include "int.h"
#include "intcop.h"
#include "intvfpu.h"
#include "intfpu.h"

typedef enum
{
    Imme,
    Spec,
    Spe2,
    Spe3,
    SrlRotr,
    SrlvRotrv,
    RegI,
    Cop0,
    Cop0CO,
    Cop1,
    Cop1BC,
    Cop1S,
    Cop1W,
    Cop2,
    Cop2BC2,
    VFPU0,
    VFPU1,
    VFPU3,
    VFPU4,
    VFPU4_0,
    VFPU4_1,
    VFPU4_2,
    VFPU5,
    VFPU5_3,
    VFPU6,
    VFPU6_1,
    VFPU6_2,
    VFPU6_3,
    VFPU6_5,
    VFPU6_7,
    VFPU6_7_0,
    ALLEGREX0,
    NumEncodings
} MipsEncoding;

typedef struct
{
    s32 altEncoding;
    const char *name;
    IntFunc interpret;
    DbgFunc debug;
} MIPSInstruction;

typedef struct
{
    MIPSInstruction *table;
    u8 shift;
    u8 mask;
} Encoding;

#define INVALID {-2, 0, 0, 0}
#define ENCODING(a) {a, 0, 0, 0}
#define INSTR(name, inter, dbg) {-1, name, inter, dbg}

MIPSInstruction tableImmediate[64] =
{
    /* 0 */
    ENCODING(Spec),
    ENCODING(RegI),
    INSTR("j",      int_j,      dbg_j),
    INSTR("jal",    int_jal,    dbg_jal),
    INSTR("beq",    int_beq,    dbg_beq),
    INSTR("bne",    int_bne,    dbg_bne),
    INSTR("blez",   int_blez,   dbg_blez),
    INSTR("bgtz",   int_bgtz,   dbg_bgtz),
    /* 8 */
    INSTR("addi",   int_addi,   dbg_addi),
    INSTR("addiu",  int_addiu,  dbg_addiu),
    INSTR("slti",   int_slti,   dbg_slti),
    INSTR("sltiu",  int_sltiu,  dbg_sltiu),
    INSTR("andi",   int_andi,   dbg_andi),
    INSTR("ori",    int_ori,    dbg_ori),
    INSTR("xori",   int_xori,   dbg_xori),
    INSTR("lui",    int_lui,    dbg_lui),
    /* 16 */
    ENCODING(Cop0),
    ENCODING(Cop1),
    ENCODING(Cop2),
    INSTR("Cop3",   NULL,       NULL),
    INSTR("beql",   int_beql,   dbg_beql),
    INSTR("bnel",   int_bnel,   dbg_bnel),
    INSTR("blezl",  int_blezl,  dbg_blezl),
    INSTR("bgtzl",  int_bgtzl,  dbg_bgtzl),
    /* 24 */
    ENCODING(VFPU0),
    ENCODING(VFPU1),
    INVALID,
    ENCODING(VFPU3),
    ENCODING(Spe2),
    INVALID,
    INVALID,
    ENCODING(Spe3),
    /* 32 */
    INSTR("lb",     int_lb,     dbg_lb),
    INSTR("lh",     int_lh,     dbg_lh),
    INSTR("lwl",    int_lwl,    dbg_lwl),
    INSTR("lw",     int_lw,     dbg_lw),
    INSTR("lbu",    int_lbu,    dbg_lbu),
    INSTR("lhu",    int_lhu,    dbg_lhu),
    INSTR("lwr",    int_lwr,    dbg_lwr),
    INSTR("lwu",    NULL,       NULL),
    /* 40 */
    INSTR("sb",     int_sb,     dbg_sb),
    INSTR("sh",     int_sh,     dbg_sh),
    INSTR("swl",    int_swl,    dbg_swl),
    INSTR("sw",     int_sw,     dbg_sw),
    INSTR("sdl",    NULL,       NULL),
    INSTR("sdr",    NULL,       NULL),
    INSTR("swr",    int_swr,    dbg_swr),
    INSTR("cache",  int_cache,  dbg_cache),
    /* 48 */
    INSTR("ll",     NULL,       NULL),
    INSTR("lwc1",   int_lwc1,   dbg_lwc1),
    INSTR("lv.s",   int_lvs,    NULL),
    INSTR("pref",   int_pref,   dbg_pref),
    ENCODING(VFPU4),
    INSTR("ulv.q",  int_ulvq,   NULL),
    INSTR("lv.q",   int_lvq,    NULL),
    ENCODING(VFPU5),
    /* 56 */
    INSTR("sc",     NULL,       NULL),
    INSTR("swc1",   int_swc1,   dbg_swc1),
    INSTR("sv.s",   int_svs,    NULL),
    INVALID,
    ENCODING(VFPU6),
    INSTR("usv.q",  int_usvq,   NULL),
    INSTR("sv.q",   int_svq,    NULL),
    INSTR("vflush", NULL,       NULL)
    /* 64 */
};

MIPSInstruction tableSpecial[64] =
{
    /* 0 */
    INSTR("sll",     int_sll,     dbg_sll),
    INVALID,
    ENCODING(SrlRotr),
    INSTR("sra",     int_sra,     dbg_sra),
    INSTR("sllv",    int_sllv,    dbg_sllv),
    INVALID,
    ENCODING(SrlvRotrv),
    INSTR("srav",    int_srav,    dbg_srav),
    /* 8 */
    INSTR("jr",      int_jr,      dbg_jr),
    INSTR("jalr",    int_jalr,    dbg_jalr),
    INSTR("movz",    int_movz,    dbg_movz),
    INSTR("movn",    int_movn,    dbg_movn),
    INSTR("syscall", int_syscall, dbg_syscall),
    INSTR("break",   int_break,   dbg_break),
    INVALID,
    INSTR("sync",    NULL,        NULL),
    /* 16 */
    INSTR("mfhi",    int_mfhi,    dbg_mfhi),
    INSTR("mthi",    int_mthi,    dbg_mthi),
    INSTR("mflo",    int_mflo,    dbg_mflo),
    INSTR("mtlo",    int_mtlo,    dbg_mtlo),
    INVALID,
    INVALID,
    INSTR("clz",     int_clz,     dbg_clz),
    INSTR("clo",     int_clo,     dbg_clo),
    /* 24 */
    INSTR("mult",    int_mult,    dbg_mult),
    INSTR("multu",   int_multu,   dbg_multu),
    INSTR("div",     int_div,     dbg_div),
    INSTR("divu",    int_divu,    dbg_divu),
    INSTR("madd",    int_madd,    dbg_madd),
    INSTR("maddu",   int_maddu,   dbg_maddu),
    INVALID,
    INVALID,
    /* 32 */
    INSTR("add",     int_add,     dbg_add),
    INSTR("addu",    int_addu,    dbg_addu),
    INSTR("sub",     int_sub,     dbg_sub),
    INSTR("subu",    int_subu,    dbg_subu),
    INSTR("and",     int_and,     dbg_and),
    INSTR("or",      int_or,      dbg_or),
    INSTR("xor",     int_xor,     dbg_xor),
    INSTR("nor",     int_nor,     dbg_nor),
    /* 40 */
    INVALID,
    INVALID,
    INSTR("slt",     int_slt,     dbg_slt),
    INSTR("sltu",    int_sltu,    dbg_sltu),
    INSTR("max",     int_max,     dbg_max),
    INSTR("min",     int_min,     dbg_min),
    INSTR("msub",    int_msub,    dbg_msub),
    INSTR("msubu",   int_msubu,   dbg_msubu),
    /* 48 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 56 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 64 */
};

MIPSInstruction tableSpecial2[64] =
{
    /* 0 */
    INSTR("halt", NULL, NULL),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 32 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INSTR("mfic", int_mfic, dbg_mfic),
    INVALID,
    INSTR("mtic", int_mtic, dbg_mtic),
    INVALID,
    /* 40 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 48 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 56 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 64 */
};

MIPSInstruction tableSpecial3[64] =
{
    /* 0 */
    INSTR("ext",  int_ext, dbg_ext),
    INVALID,
    INVALID,
    INVALID,
    INSTR("ins",  int_ins, dbg_ins),
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    ENCODING(ALLEGREX0),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 32 */
    ENCODING(ALLEGREX0),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 40 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 48 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 56 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 64 */
};

MIPSInstruction tableSrlRotr[32] =
{
    /* 0 */
    INSTR("srl",  int_srl,  dbg_srl),
    INSTR("rotr", int_rotr, dbg_rotr),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 32 */
};

MIPSInstruction tableSrlvRotrv[32] =
{
    /* 0 */
    INSTR("srlv",  int_srlv,  dbg_srlv),
    INSTR("rotrv", int_rotrv, dbg_rotrv),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 32 */
};

MIPSInstruction tableRegImm[32] =
{
    /* 0 */
    INSTR("bltz",  int_bltz,  dbg_bltz),
    INSTR("bgez",  int_bgez,  dbg_bgez),
    INSTR("bltzl", int_bltzl, dbg_bltzl),
    INSTR("bgezl", int_bgezl, dbg_bgezl),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INSTR("[unk regimm 0xf]", NULL, NULL),
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

MIPSInstruction tableCop2[32] =
{
    /* 0 */
    INVALID,
    INVALID,
    INVALID,
    INSTR("mfv",  int_mfv, dbg_mfv),
    INVALID,
    INVALID,
    INVALID,
    INSTR("mtv",  int_mtv, dbg_mtv),
    /* 8 */
    ENCODING(Cop2BC2),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

MIPSInstruction tableCop2BC2[4] =
{
    /* 0 */
    INSTR("bvf",  int_bvf,  dbg_bvf),
    INSTR("bvt",  int_bvt,  dbg_bvt),
    INSTR("bvfl", int_bvfl, dbg_bvfl),
    INSTR("bvtl", int_bvtl, dbg_bvtl)
    /* 4 */
};

MIPSInstruction tableCop0[32] =
{
    /* 0 */
    INSTR("mfc0", int_mfc0, dbg_mfc0),
    INVALID,
    INSTR("cfc0", int_cfc0, dbg_cfc0),
    INVALID,
    INSTR("mtc0", int_mtc0, dbg_mtc0),
    INVALID,
    INSTR("ctc0", int_ctc0, dbg_ctc0),
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    /* 24 */
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO),
    ENCODING(Cop0CO)
    /* 32 */
};

MIPSInstruction tableCop0CO[64] =
{
    /* 0 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 32 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 40 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 48 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 56 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 64 */
};

MIPSInstruction tableCop1[32] =
{
    /* 0 */
    INSTR("mfc1",  int_mfc1, dbg_mfc1),
    INSTR("dmfc1", NULL,     NULL),
    INSTR("cfc1",  int_cfc1, dbg_cfc1),
    INVALID,
    INSTR("mtc1",  int_mtc1, dbg_mtc1),
    INSTR("dmtc1", NULL,     NULL),
    INSTR("ctc1",  int_ctc1, dbg_ctc1),
    INVALID,
    /* 8 */
    ENCODING(Cop1BC),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    ENCODING(Cop1S),
    INVALID,
    INVALID,
    INVALID,
    ENCODING(Cop1W),
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

MIPSInstruction tableCop1BC[32] =
{
    /* 0 */
    INSTR("bc1f",  int_bc1f,  dbg_bc1f),
    INSTR("bc1t",  int_bc1t,  dbg_bc1t),
    INSTR("bc1fl", int_bc1fl, dbg_bc1fl),
    INSTR("bc1tl", int_bc1tl, dbg_bc1tl),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

MIPSInstruction tableCop1S[64] =
{
    /* 0 */
    INSTR("add.s",       int_fadd,     dbg_fadd),
    INSTR("sub.s",       int_fsub,     dbg_fsub),
    INSTR("mul.s",       int_fmul,     dbg_fmul),
    INSTR("div.s",       int_fdiv,     dbg_fdiv),
    INSTR("sqrt.s",      int_sqrt,     dbg_sqrt),
    INSTR("abs.s",       int_abs,      dbg_abs),
    INSTR("mov.s",       int_mov,      dbg_mov),
    INSTR("neg.s",       int_neg,      dbg_neg),
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INSTR("round.w.s",   int_round,    dbg_round),
    INSTR("trunc.w.s",   int_trunc,    dbg_trunc),
    INSTR("ceil.w.s",    int_ceil,     dbg_ceil),
    INSTR("floor.w.s",   int_floor,    dbg_floor),
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 32 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INSTR("cvt.w.s",     int_cvtws,    dbg_cvtws),
    INVALID,
    INVALID,
    INVALID,
    /* 40 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 48 */
    INVALID,
    INVALID,
    INSTR("c.eq.s",      int_eq,       dbg_eq),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 56 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INSTR("c.lt.s",      int_lt,       dbg_lt),
    INSTR("c.nge.s",     int_nge,      dbg_nge),
    INSTR("c.le.s",      int_le,       dbg_le),
    INSTR("c.ngt.s",     int_ngt,      dbg_ngt)
    /* 64 */
};

MIPSInstruction tableCop1W[64] =
{
    /* 0 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 32 */
    INSTR("cvt.s.w",     int_cvtsw,    dbg_cvtsw),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 40 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 48 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 56 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 64 */
};

MIPSInstruction tableVFPU0[8] =
{
    /* 0 */
    INSTR("vadd", int_vadd, NULL),
    INSTR("vsub", int_vsub, NULL),
    INSTR("vsbn", NULL,     NULL),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INSTR("vdiv", int_vdiv, NULL)
    /* 8 */
};

MIPSInstruction tableVFPU1[8] =
{
    /* 0 */
    INSTR("vmul", int_vmul, NULL),
    INSTR("vdot", int_vdot, NULL),
    INSTR("vscl", int_vscl, NULL),
    INVALID,
    INSTR("vhdp", NULL,     NULL),
    INSTR("vcrs", int_vcrs, NULL),
    INSTR("vdet", NULL,     NULL),
    INVALID
    /* 8 */
};

MIPSInstruction tableVFPU3[8] =
{
    /* 0 */
    INSTR("vcmp",  int_vcmp, NULL),
    INVALID,
    INSTR("vmin",  NULL,     NULL),
    INSTR("vmax",  NULL,     NULL),
    INVALID,
    INSTR("vscmp", NULL,     NULL),
    INSTR("vsge",  NULL,     NULL),
    INSTR("vslt",  NULL,     NULL)
    /* 8 */
};

MIPSInstruction tableVFPU4[32] =
{
    /* 0 */
    ENCODING(VFPU4_0),
    ENCODING(VFPU4_1),
    ENCODING(VFPU4_2),
    INSTR("vcst",   int_vcst,  NULL),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INSTR("vf2in",  int_vf2in, NULL),
    INSTR("vf2iz",  int_vf2iz, NULL),
    INSTR("vf2iu",  int_vf2iu, NULL),
    INSTR("vf2id",  int_vf2id, NULL),
    INSTR("vi2f",   int_vi2f,  NULL),
    INSTR("vcmov",  int_vcmov, NULL),
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

MIPSInstruction tableVFPU4_0[32] =
{
    /* 0 */
    INSTR("vmov",   int_vmov,   NULL),
    INSTR("vabs",   int_vabs,   NULL),
    INSTR("vneg",   int_vneg,   NULL),
    INSTR("vidt",   int_vidt,   NULL),
    INSTR("vsat0",  int_vsat0,  NULL),
    INSTR("vsat1",  int_vsat1,  NULL),
    INSTR("vzero",  int_vzero,  NULL),
    INSTR("vone",   int_vone,   NULL),
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INSTR("vrcp",   int_vrcp,   NULL),
    INSTR("vrsq",   int_vrsq,   NULL),
    INSTR("vsin",   int_vsin,   NULL),
    INSTR("vcos",   int_vcos,   NULL),
    INSTR("vexp2",  int_vexp2,  NULL),
    INSTR("vlog2",  int_vlog2,  NULL),
    INSTR("vsqrt",  int_vsqrt,  NULL),
    INSTR("vasin",  int_vasin,  NULL),
    /* 24 */
    INSTR("vnrcp",  int_vnrcp,  NULL),
    INVALID,
    INSTR("vnsin",  int_vnsin,  NULL),
    INVALID,
    INSTR("vrexp2", int_vrexp2, NULL),
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

MIPSInstruction tableVFPU4_1[32] =
{
    /* 0 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INSTR("vi2uc", int_vi2uc, NULL),
    INSTR("vi2c",  int_vi2c,  NULL),
    INSTR("vi2us", int_vi2us, NULL),
    INSTR("vi2s",  int_vi2s,  NULL)
    /* 32 */
};

MIPSInstruction tableVFPU4_2[32] =
{
    /* 0 */
    INVALID,
    INVALID,
    INSTR("vbfy1",  int_vbfy1, NULL),
    INSTR("vbfy2",  int_vbfy2, NULL),
    INVALID,
    INVALID,
    INSTR("vfad",   int_vfad,  NULL),
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

MIPSInstruction tableVFPU5[4] =
{
    /* 0 */
    INSTR("vpfxs",    int_vpfxs, NULL),
    INSTR("vpfxt",    int_vpfxt, NULL),
    INSTR("vpfxd",    int_vpfxd, NULL),
    ENCODING(VFPU5_3)
    /* 4 */
};

MIPSInstruction tableVFPU5_3[2] =
{
    INSTR("viim", int_viim, NULL),
    INSTR("vfim", int_vfim, NULL)
};

MIPSInstruction tableVFPU6[8] =
{
    /* 0 */
    INSTR("vmmul",     int_vmmul,     NULL),
    ENCODING(VFPU6_1),
    ENCODING(VFPU6_2),
    ENCODING(VFPU6_3),
    INVALID,
    ENCODING(VFPU6_5),
    INVALID,
    ENCODING(VFPU6_7)
    /* 8 */
};

MIPSInstruction tableVFPU6_1[2] =
{
    INSTR("vhtfm2", int_vhtfm2, NULL),
    INSTR("vtfm2",  int_vtfm2,  NULL)
};

MIPSInstruction tableVFPU6_2[2] =
{
    INSTR("vhtfm3", int_vhtfm3, NULL),
    INSTR("vtfm3",  int_vtfm3,  NULL)
};

MIPSInstruction tableVFPU6_3[2] =
{
    INSTR("vhtfm4", int_vhtfm4, NULL),
    INSTR("vtfm4",  int_vtfm4,  NULL)
};

MIPSInstruction tableVFPU6_5[2] =
{
    INSTR("vcrsp", int_vcrsp, NULL),
    INSTR("vqmul", int_vqmul, NULL)
};

MIPSInstruction tableVFPU6_7[4] =
{
    /* 0 */
    ENCODING(VFPU6_7_0),
    INSTR("vrot", int_vrot, NULL),
    INVALID,
    INVALID
    /* 4 */
};

MIPSInstruction tableVFPU6_7_0[16] =
{
    /* 0 */
    INSTR("vmmov",  int_vmmov,  NULL),
    INVALID,
    INVALID,
    INSTR("vmidt",  int_vmidt,  NULL),
    INVALID,
    INVALID,
    INSTR("vmzero", int_vmzero, NULL),
    INSTR("vmone",  int_vmone,  NULL),
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 16 */
};

MIPSInstruction tableAllegrex[32] =
{
    /* 0 */
    INVALID,
    INVALID,
    INSTR("wsbh",   int_wsbh, dbg_wsbh),
    INSTR("wsbw",   int_wsbw, dbg_wsbw),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 8 */
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    /* 16 */
    INSTR("seb",    int_seb, dbg_seb),
    INVALID,
    INVALID,
    INVALID,
    INSTR("bitrev", int_bitrev, dbg_bitrev),
    INVALID,
    INVALID,
    INVALID,
    /* 24 */
    INSTR("seh",    int_seh, dbg_seh),
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID,
    INVALID
    /* 32 */
};

Encoding encodings[NumEncodings] =
{
    /*                            xxxx xxss ssst tttt dddd dyyy yyzz zzzz */
    {tableImmediate,   26, 6}, /* ++++ ++00 0000 0000 0000 0000 0000 0000 */
    {tableSpecial,     0,  6}, /* 0000 0000 0000 0000 0000 0000 00++ ++++ */
    {tableSpecial2,    0,  6}, /* 0000 0000 0000 0000 0000 0000 00++ ++++ */
    {tableSpecial3,    0,  6}, /* 0000 0000 0000 0000 0000 0000 00++ ++++ */
    {tableSrlRotr,     21, 5}, /* 0000 00++ +++0 0000 0000 0000 0000 0000 */
    {tableSrlvRotrv,   6,  5}, /* 0000 0000 0000 0000 0000 0+++ ++00 0000 */
    {tableRegImm,      16, 5}, /* 0000 0000 000+ ++++ 0000 0000 0000 0000 */
    {tableCop0,        21, 5}, /* 0000 00++ +++0 0000 0000 0000 0000 0000 */
    {tableCop0CO,      0,  6}, /* 0000 0000 0000 0000 0000 0000 00++ ++++ */
    {tableCop1,        21, 5}, /* 0000 00++ +++0 0000 0000 0000 0000 0000 */
    {tableCop1BC,      16, 5}, /* 0000 0000 000+ ++++ 0000 0000 0000 0000 */
    {tableCop1S,       0,  6}, /* 0000 0000 0000 0000 0000 0000 00++ ++++ */
    {tableCop1W,       0,  6}, /* 0000 0000 0000 0000 0000 0000 00++ ++++ */
    {tableCop2,        21, 5}, /* 0000 00++ +++0 0000 0000 0000 0000 0000 */
    {tableCop2BC2,     16, 2}, /* 0000 0000 0000 00++ 0000 0000 0000 0000 */
    {tableVFPU0,       23, 3}, /* 0000 00++ +000 0000 0000 0000 0000 0000 */
    {tableVFPU1,       23, 3}, /* 0000 00++ +000 0000 0000 0000 0000 0000 */
    {tableVFPU3,       23, 3}, /* 0000 00++ +000 0000 0000 0000 0000 0000 */
    {tableVFPU4,       21, 5}, /* 0000 00++ +++0 0000 0000 0000 0000 0000 */
    {tableVFPU4_0,     16, 5}, /* 0000 0000 000+ ++++ 0000 0000 0000 0000 */
    {tableVFPU4_1,     16, 5}, /* 0000 0000 000+ ++++ 0000 0000 0000 0000 */
    {tableVFPU4_2,     16, 5}, /* 0000 0000 000+ ++++ 0000 0000 0000 0000 */
    {tableVFPU5,       24, 2}, /* 0000 00++ 0000 0000 0000 0000 0000 0000 */
    {tableVFPU5_3,     23, 1}, /* 0000 0000 +000 0000 0000 0000 0000 0000 */
    {tableVFPU6,       23, 3}, /* 0000 00++ +000 0000 0000 0000 0000 0000 */
    {tableVFPU6_1,     7,  1}, /* 0000 0000 0000 0000 0000 0000 +000 0000 */
    {tableVFPU6_2,     7,  1}, /* 0000 0000 0000 0000 0000 0000 +000 0000 */
    {tableVFPU6_3,     7,  1}, /* 0000 0000 0000 0000 0000 0000 +000 0000 */
    {tableVFPU6_5,     7,  1}, /* 0000 0000 0000 0000 0000 0000 +000 0000 */
    {tableVFPU6_7,     21, 2}, /* 0000 0000 0++0 0000 0000 0000 0000 0000 */
    {tableVFPU6_7_0,   16, 3}, /* 0000 0000 0000 0+++ 0000 0000 0000 0000 */
    {tableAllegrex,    6,  5}  /* 0000 0000 0000 0000 0000 0+++ ++00 0000 */
};

MIPSInstruction *cpu_getinstr(u32 op)
{
    MipsEncoding encoding = Imme;
    MIPSInstruction *instr = &tableImmediate[op >> 26];
    while (instr->altEncoding != -1)
    {
        MIPSInstruction *table = encodings[encoding].table;
        u32 mask = ((1 << encodings[encoding].mask) - 1);
        u32 shift = encodings[encoding].shift;
        u32 subop = (op >> shift) & mask;
        instr = &table[subop];
        if (instr->altEncoding == -2)
            return NULL;
        encoding = (MipsEncoding)instr->altEncoding;
    }
    return instr;
}

void cpu_interpret(u32 op)
{
    MIPSInstruction *instr = cpu_getinstr(op);

    if (!instr)
        _log(ERR, CPU, "Unrecognized instruction [%08x]!", op);
    else if (!instr->interpret)
        _log(ERR, CPU, "Instruction '%s' not implemented", instr->name);
    else
        instr->interpret(op);
}

void cpu_debug(char *string, u32 len, u32 op)
{
    MIPSInstruction *instr = cpu_getinstr(op);

    if (!instr)
        strcpy(string, "(none)");
    else if (!instr->debug)
        sprintf(string, "DEBUG NOT DONE (%s)", instr->name);
    else
        instr->debug(string, len, op);
}

